//Compile with ~'g++ -o vorbisstreamer main.cpp -lvorbis -lvorbisfile -lvorbisenc'
//Encode mp3/m4a ~'ffmpeg -i datei.mp3 -ar 44100 -aq 6 -acodec libvorbis datei.ogg'

#ifdef __APPLE__
	#import <Vorbis/codec.h>
	#import <Vorbis/vorbisfile.h>
	#import <Vorbis/vorbisenc.h>
	#define MSG_NOSIGNAL 0
#else
	#include <vorbis/codec.h>
	#include <vorbis/vorbisfile.h>
	#include <vorbis/vorbisenc.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/time.h>
#include <pthread.h>
//SETTINGS
const int channels=2;
const int frequency=44100;
const unsigned short int port=8888;
#define maxConnections 32
//should not be changed
#define chunked 0
const int offset=1;
//Ogg
vorbis_info vorbisInfo;
vorbis_dsp_state encoder;
OggVorbis_File decoder;
ogg_stream_state streamState;
int current_section;


//Server

int server;
int connections[maxConnections];
ogg_stream_state streams[maxConnections];
int connectionCount=0;
pthread_t acceptLoop;

//Debug
int outputtedPages=0;
char timestampBuffer[12];


void* acceptConnections(void*);

const char* timestamp()
{
	time_t x = time (NULL);
	struct tm * timeinfo = localtime ( &x ); 
	sprintf(timestampBuffer,"[%02d:%02d:%02d] ", timeinfo->tm_hour,timeinfo->tm_min,timeinfo->tm_sec); 
	return timestampBuffer;
}

void openFile(const char* url)
{
	if(ov_open(fopen(url,"r"), &decoder, NULL, 0) < 0) 
	{
		fprintf(stderr,"Input does not appear to be an Ogg bitstream.\n");
 		exit(1);
	}
	
	//Ogg Informationen auslesen
	
	/*char **ptr=ov_comment(&decoder,-1)->user_comments;
	 vorbis_info *vi=ov_info(&decoder,-1);
	 while(*ptr)
	 {
	 fprintf(stderr,"%s\n",*ptr);
	 ++ptr;
	 }
	 fprintf(stderr,"\nBitstream is %d channel, %ldHz\n",vi->channels,vi->rate);
	 fprintf(stderr,"\nDecoded length: %ld samples\n",(long)ov_pcm_total(&decoder,-1));
	 fprintf(stderr,"Encoded by: %s\n\n",ov_comment(&decoder,-1)->vendor);
	 */
}

void getRawAudio(float*** pcm,long* size)
{
	//while(!eof)
	{
		long ret=ov_read_float(&decoder,pcm,8192,&current_section);
		*size=ret;
		if (ret < 0) 
			fprintf(stderr,"\nIrgendein Fehler");
	}
	
	
}

void closeFile()
{
	ov_clear(&decoder);
}

void closeConnection(int i)
{
	close(connections[i]);
	connections[i]=0;
	ogg_stream_clear(&streams[i]);
	fprintf(stderr,"%sclosed connection.\n",timestamp());
	connectionCount--;
	fprintf(stderr,"%sserverstatus: %d/%d\n",timestamp(),connectionCount,maxConnections);
}

void setBlocking(int socket)
{
	int flags = fcntl(server, F_GETFL, 0);
	if(flags & O_NONBLOCK) fcntl(server, F_SETFL, flags - O_NONBLOCK);
	struct timeval tim;
	tim.tv_sec=0;
	tim.tv_usec=500;
	setsockopt(socket,IPPROTO_TCP,SO_SNDTIMEO,&tim,sizeof(tim));
}

void setNonBlocking()
{
	int flags = fcntl(server, F_GETFL, 0);
	fcntl(server, F_SETFL, flags | O_NONBLOCK);
}

int sendAll(int socket, const char* const buf, const int size)
{
	errno=0;
	int bytesSent = 0; // Anzahl Bytes die wir bereits vom Buffer gesendet haben
	int tmp=0;
    do
    { 
		tmp=send(socket, buf + bytesSent, size - bytesSent, MSG_NOSIGNAL);//$20000);
		if(tmp==-1) {if(errno!=EPIPE) perror("SENDALL");return -1;}
		bytesSent += tmp;
    } while(bytesSent < size);
	return bytesSent;
}

void outputPage(int i,ogg_page* page)
{
	errno=0;
	if(chunked)
	{
		int size=page->header_len+page->body_len;
		char buffer[10];
		int datalength=sprintf(buffer,"%X\r\n",size);
		sendAll(connections[i],buffer,datalength);
	}/*
	  sendAll(connections[i],page->header,page->header_len);
	  if(errno==EAGAIN)
	  {
	  closeConnection(i);
	  }
	  else
	  {
	  sendAll(connections[i],page->body,page->body_len);
	  if(errno==EAGAIN)
	  {
	  closeConnection(i);	
	  }
	  else
	  {
	  if(chunked) sendAll(connections[i],"\r\n",2);
	  }
	  outputtedPages++;
	  }*/
	
	int ret1=sendAll(connections[i],page->header,page->header_len);
	int ret2=sendAll(connections[i],page->body,page->body_len);
	if(ret2==-1)
	{
		if(errno==32)
			closeConnection(i);
	}
}

void outputPacket(int i,ogg_packet* packet)
{
	ogg_stream_packetin(&streams[i],packet);
	ogg_page page;
	if(ogg_stream_pageout(&streams[i],&page)!=0)
	{
		outputPage(i,&page);
	}
}

void outputPackets(ogg_packet* packet)
{
	for(int i=0;i<maxConnections;i++)
	{
		if(connections[i]!=0)
		{
			//fprintf(stderr,"AUSGABE");
			ogg_stream_packetin(&streams[i],packet);
			ogg_page page;
			if(ogg_stream_pageout(&streams[i],&page)!=0)
			{
				outputPage(i,&page);
			}
		}
	}
}

void outputHeader(int nr)
{
	if(connections[nr]==0) return;
	vorbis_comment comment;
	ogg_packet header1,header2,header3;
	vorbis_comment_init(&comment);
	vorbis_analysis_headerout(&encoder,&comment,&header1,&header2,&header3);
	
	outputPacket(nr,&header1);
	outputPacket(nr,&header2);
	outputPacket(nr,&header3);
}

void outputFooter()
{
	
}


void launchServer(float quality)
{
	
	vorbis_info_init(&vorbisInfo);
	fprintf(stderr,"%ssettings: number of channels: %d, frequency: %d, quality: %.1f\n\n",
			timestamp(),channels,frequency,quality);
	vorbis_encode_init_vbr(&vorbisInfo,channels,frequency,quality);
	//vorbis_encode_init(&vorbisInfo,2,44100,128000,128000,128000);
	vorbis_analysis_init(&encoder,&vorbisInfo);
	
	signal(SIGPIPE,SIG_IGN);
	server=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if (server == -1)
	{
		perror("socket() failed");
	}
	//setNonBlocking(server);
	int one=1;
	setsockopt(server,IPPROTO_TCP,SO_REUSEADDR,&one,sizeof(int));
#ifdef __APPLE__
	setsockopt(server,IPPROTO_TCP,SO_NOSIGPIPE,&one,sizeof(int));
#endif
	struct sockaddr_in addr;
	addr.sin_addr.s_addr=INADDR_ANY;
	addr.sin_port=htons(port);
	addr.sin_family=AF_INET;
	int ret=bind(server,(const struct sockaddr *)&addr,sizeof(addr));
	if(ret==-1) 
	{
		fprintf(stderr,"port %d already in use.\n",port);
		exit(1);
	}
	listen(server,32);
	
	fprintf(stderr,"%screated socket.\n",timestamp());
	for(int i=0;i<maxConnections;i++) connections[i]=0;
	printf("%d",pthread_create(&acceptLoop,0,&acceptConnections,0));
}

void httpHeader(int nr)
{
	//const char* header="HTTP/1.1 206 Partial Content\r\nServer : VorbisStreamer\r\nContent-Type : application/ogg\r\nCache-Control: no-cache, must-revalidate\r\nExpires : Sat, 26 Jul 1997 05:00:00 GMT\r\nConnection : Keep-Alive\r\n\r\n";
	//const char* header="HTTP/1.1 200 OK\r\nServer:VorbisStreamer\r\nContent-Type:application/ogg\r\nAccept-Ranges:bytes\r\nTransfer-Encoding:chunked\r\nX-Content-Duration:0\r\nCache-Control: no-cache, must-revalidate\r\nAge:0\r\nExpires: Sat, 26 Jul 1997 05:00:00 GMT\r\nConnection: close\r\n\r\n";
	const char* header="HTTP/1.0 200 OK\r\nServer:VorbisStreamer\r\nContent-Type:application/ogg\r\n\r\n";
	sendAll(connections[nr],header,strlen(header));
	fprintf(stderr,"%shttp-header sent.\n",timestamp());
}


//returns true if there was a new client, ignoring whether it connected successfully
int acceptConnection()
{
	int newConnection=accept(server,NULL,NULL);
	
	if(newConnection>0)
	{
		if(connectionCount<=maxConnections)
		{
			for(int i=0;i<maxConnections;i++)
			{
				if(connections[i]==0)
				{
#ifdef __APPLE__
					int one=1;
					setsockopt(newConnection,IPPROTO_TCP,SO_NOSIGPIPE,&one,sizeof(int));
#endif
					char buffer[4096];
					int size=0;
					do
					{
						size=recv(newConnection,buffer,4096,0);
					}
					while(size==0);
					
					if(strncmp(buffer,"GET /stream.ogg",15)==0)
					{
						fprintf(stderr,"%snew connection\n",timestamp());
						connections[i]=newConnection;
						connectionCount++;
						
						ogg_stream_init(&streams[i],42);
						
						//httpHeader might close the connection, so everything 
						//needs to be ready for closeconnection to clean it up again
						httpHeader(i);
						outputHeader(i);
						
						fprintf(stderr,"%sserverstatus: %d/%d\n",timestamp(),connectionCount,maxConnections);
					}
					else
					{
						fprintf(stderr,"%signored request for invalid url.\n",timestamp());
						close(newConnection);
						connections[i]=0;
					}
					break;
				}	
			}
		}
		else
		{
			close(newConnection);
			fprintf(stderr,"%snew connection, but server is full\n",timestamp());
		}
		return 1;
	}
	else 
	{
		return 0;
	}

}

void* acceptConnections(void* a)
{
	
	while(1)
	{
		acceptConnection();
		sleep(1);
		
	}
		
}

void streamFile(const char* url,int* skip)
{
	fprintf(stderr,"%sstreaming %s.\n",timestamp(),url);
	openFile(url);
	vorbis_block block;
	int dbg=vorbis_block_init(&encoder,&block);
	float** pcm;//=new char[4096];
	long size=0;
	
	long starttime=time(NULL);
	long samples=0;
	
	
	for(getRawAudio(&pcm,&size);size>0;getRawAudio(&pcm,&size))
	{
		samples+=size;
		float** rawData=vorbis_analysis_buffer(&encoder,size);
		for(int i=0;i<2;i++)
		{	
			for(int j=0;j<size;j++)
			{	
				rawData[i][j]=pcm[i][j];
			}
		}
		//delete pcm;
		vorbis_analysis_wrote(&encoder,size);		
		while(vorbis_analysis_blockout(&encoder,&block)==1)
		{
			ogg_packet packet;
			dbg=vorbis_analysis(&block,NULL);
			//fprintf(stderr,"\n%d",dbg);
			if(dbg==OV_EFAULT) fprintf(stderr,"INTERNAL ERROR");
			if(dbg==OV_EINVAL) fprintf(stderr,"INVALID DATA");
			vorbis_bitrate_addblock(&block);
			while(vorbis_bitrate_flushpacket(&encoder,&packet))
			{
				outputPackets(&packet);
			}
		}
		
		//wait
		//frequency=samples/time <=> time=samples/frequency
		long playedTime=samples/frequency;
		long currentTime=time(NULL)-starttime;
		
		//fprintf(stderr,"%d, %d-%d=%d\n",starttime,playedTime,currentTime,playedTime-currentTime);
		
		//accept new http requests
		if(playedTime-offset-currentTime>0) sleep(playedTime-offset-currentTime);
		if(*skip)
			break;
		//while(acceptConnections());
	}
	//warten, damit das streamen einer datei genauso lange dauert wie die datei lang ist
	long playedTime=samples/frequency;
	long currentTime=time(NULL)-starttime;
	if(playedTime-currentTime>0) sleep(playedTime-currentTime);
	vorbis_block_clear(&block);
	closeFile();
}

void closeServer()
{
	outputFooter();
	vorbis_dsp_clear(&encoder);
	vorbis_info_clear(&vorbisInfo);	
	fprintf(stderr,"%soutputted pages: %d\n",timestamp(),outputtedPages);
	fprintf(stderr,"%sshut down.\n",timestamp());
	for(int i=0;i<maxConnections;i++)
	{
		if(connections[i]!=0)
			close(connections[i]);
	}
	close(server);
	fprintf(stderr,"bye bye.\n");
	
}
void crashServer()
{	
	closeServer();
	exit(0);
}
